#include "common/pch.h"
#include "player/playermgr.h"

#include <format>

#include <cftc/encoding/json.h>
#include <cftf/loger/syslog.h>
#include <cftf/mysql/dbpool.h>
#include <cftc/util/strutils.h>
#include <cftf/net/msghdr.h>
#include <cftf/timer/timer.h>

#include "cs/cs_message.h"
#include "listener/gglistener.h"
#include "listener/ggsession.h"
#include "auth/sha1.h"
#include "auth/bignumber.h"
#include "metadata/item.h"
#include "metadata/areatrigger_teleport.h"
#include "player/session.h"
#include "object/objectguid.h"
#include "object/player.h"
#include "world/world.h"
#include "dbc/structure.h"
#include "map/map.h"

namespace player
{

	void PlayerMgr::Init()
	{

	}

	void PlayerMgr::UnInit()
	{
	}

	void PlayerMgr::_CMAuthSession(const cftf::net::MsgHdr* hdr, const std::shared_ptr<cs::CMAuthSession>& msg)
	{
		cftf::loger::SysLog::Instance().Info(cftc::encoding::Json::Marshal(msg.get()));
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Login,
			"SELECT id, gmlevel, sessionkey, last_ip, locked, v, s, mutetime,"
			"       locale, os FROM realmd.account WHERE username=:username;"
			, 
			{
				{":username", msg->account},
			},
			[socket_handle = (int)hdr->GetSocketHandle(), msg = msg]
			(const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{			
				PlayerMgr::Instance().InternalAuthSession(socket_handle, msg, rsp);				
			}
		);
	}

	void PlayerMgr::_CMCharEnum(const cftf::net::MsgHdr* hdr, const std::shared_ptr<cs::CMCharEnum>& msg)
	{
		auto tmp_session = GetSessionBySocket((int)hdr->GetSocketHandle());
		if (!tmp_session) {
			return;
		}
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.playerBytes, characters.playerBytes2, characters.level, "
			"characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, guild_member.guildid, characters.playerFlags, "
			"characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache "
			"FROM character0.characters LEFT JOIN character0.character_pet ON characters.guid=character_pet.owner AND character_pet.slot=:slot "
			"LEFT JOIN character0.guild_member ON characters.guid = guild_member.guid "
			"WHERE character0.characters.account = :account ORDER BY characters.guid"
			,
			{
				{":slot", (int)PetSaveMode_e::PET_SAVE_AS_CURRENT},
				{":account", tmp_session->account_id},
			},
			[socket_handle = (int)hdr->GetSocketHandle(), msg]
			(const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				PlayerMgr::Instance().InternalCharEnum(socket_handle, msg, rsp);				
			}
		);
	}

	void PlayerMgr::_CMPlayerLogin(const cftf::net::MsgHdr* hdr, const std::shared_ptr<cs::CMPlayerLogin>& msg)
	{
		auto session = GetSessionBySocket((int)hdr->GetSocketHandle());
		if (!session) {
			return;
		}
		if (session->Loading()) {
			cftf::loger::SysLog::Instance().Warning(std::format("Player tryes to login again, AccountId = {}", session->account_id));
			return;
		}
		session->guid = msg->player_guid;
		session->loading_ = true;
		auto guid = ObjectGuid(msg->player_guid);
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `guid`, `account`, `name`, `race`, `class`, `gender`, `level`, `xp`, `money`, `playerBytes`, `playerBytes2`, `playerFlags`,"
			"`position_x`, `position_y`, `position_z`, `map`, `orientation`, `taximask`, `cinematic`, `totaltime`, `leveltime`, `rest_bonus`, `logout_time`, `is_logout_resting`, `resettalents_cost`,"
			"`resettalents_time`, `trans_x`, `trans_y`, `trans_z`, `trans_o`, `transguid`, `extra_flags`, `stable_slots`, `at_login`, `zone`, `online`, `death_expire_time`, `taxi_path`,"
			"`honor_highest_rank`, `honor_standing`, `stored_honor_rating`, `stored_dishonorable_kills`, `stored_honorable_kills`,"
			"`watchedFaction`, `drunk`,"
			"`health`, `power1`, `power2`, `power3`, `power4`, `power5`, `exploredZones`, `equipmentCache`, `ammoId`, `actionBars`, `createdDate` FROM `characters` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session] (const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADFROM) = rsp->GetDataSet();
			}
			)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `groupId` FROM group_member WHERE `memberGuid` = :memberGuid",
			{
				{":memberGuid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADGROUP) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `id`, `permanent`, `map`, `resettime` FROM `character_instance` LEFT JOIN `instance` ON `instance` = `id` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `caster_guid`,`item_guid`,`spell`,`stackcount`,`remaincharges`,`basepoints0`,`basepoints1`,`basepoints2`,`periodictime0`,`periodictime1`,`periodictime2`,`maxduration`,`remaintime`,`effIndexMask` FROM `character_aura` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADAURAS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `spell`,`active`,`disabled` FROM `character_spell` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADSPELLS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `quest`,`status`,`rewarded`,`explored`,`timer`,`mobcount1`,`mobcount2`,`mobcount3`,`mobcount4`,`itemcount1`,`itemcount2`,`itemcount3`,`itemcount4` FROM `character_queststatus` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `victim_type`,`victim`,`honor`,`date`,`type` FROM `character_honor_cp` WHERE `used`=0 AND `guid`=:guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADHONORCP) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `faction`,`standing`,`flags` FROM `character_reputation` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADREPUTATION) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `data`,`bag`,`slot`,`item`,`item_template` FROM `character_inventory` JOIN `item_instance` ON `character_inventory`.`item` = `item_instance`.`guid` WHERE `character_inventory`.`guid` = :guid ORDER BY `bag`,`slot`",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADINVENTORY) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `guid`,`itemid`,`amount`,`property` FROM `item_loot` WHERE `owner_guid` = :owner_guid",
			{
				{":owner_guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADITEMLOOT) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `button`,`action`,`type` FROM `character_action` WHERE `guid` = :guid ORDER BY `button`",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADACTIONS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `friend`,`flags` FROM `character_social` WHERE `guid` = :guid LIMIT 255",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADSOCIALLIST) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `map`,`zone`,`position_x`,`position_y`,`position_z` FROM `character_homebind` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADHOMEBIND) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `spell`,`item`,`time` FROM `character_spell_cooldown` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `guildid`,`rank` FROM `guild_member` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADGUILD) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `instance_id`, `team`, `join_x`, `join_y`, `join_z`, `join_o`, `join_map` FROM `character_battleground_data` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADBGDATA) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `skill`, `value`, `max` FROM `character_skills` WHERE `guid` = :guid",
			{
				{":guid", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADSKILLS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `id`,`messageType`,`sender`,`receiver`,`subject`,`body`,`expire_time`,`deliver_time`,`money`,`cod`,`checked`,`stationery`,`mailTemplateId`,`has_items` FROM `mail` WHERE `receiver` = :receiver ORDER BY `id` DESC",
			{
				{":receiver", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADMAILS) = rsp->GetDataSet();
			}
		)->Await();
		cftf::mysql::DBPool::Instance().RawQuery(
			(int)DataSource_e::Character,
			"SELECT `data`, `mail_id`, `item_guid`, `item_template` FROM `mail_items` JOIN `item_instance` ON `item_guid` = `guid` WHERE `receiver` = :receiver",
			{
				{":receiver", guid.GetCounter()},
			},
			[session](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
			{
				if (rsp->IsOk()) {
					CFT_ABORT();
				}
				session->datasets.at(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS) = rsp->GetDataSet();
			}
		)->Await();
		HandlePlayerLogin(session.get());
	}

	void PlayerMgr::OnSocketDiscount(int socket_handle)
	{
		session_hash_.erase(socket_handle);
	}

	std::shared_ptr<Session> PlayerMgr::GetSessionBySocket(int socket_handle)
	{
		auto itr = session_hash_.find(socket_handle);
		return itr != session_hash_.end() ? itr->second : nullptr;
	}

	void PlayerMgr::InternalAuthSession(int socket_handle, const std::shared_ptr<cs::CMAuthSession> msg, const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
	{
		if (!rsp->IsOk()) {
			return;
		}
		if (rsp->GetDataSet()->IsEof()) {
			return;
		}
		auth::BigNumber K;
		{
			std::string sessionkey = rsp->GetDataSet()->GetByFieldName("sessionkey")->Get<std::string>();
			std::string v = rsp->GetDataSet()->GetByFieldName("v")->Get<std::string>();
			std::string s = rsp->GetDataSet()->GetByFieldName("s")->Get<std::string>();
			unsigned int m_Seed = 0;
			GGListener::Instance().TouchSession(
				socket_handle,
				[rsp, &m_Seed](GGSession* s) mutable
				{
					m_Seed = s->GetSpeed();
				}
			);

			auth::Sha1Hash sha;
			unsigned int t = 0;
			unsigned int seed = m_Seed;
			K.SetHexStr(sessionkey.c_str());

			sha.UpdateData(msg->account);
			sha.UpdateData((unsigned char*)&t, 4);
			sha.UpdateData((unsigned char*)&msg->client_seed, 4);
			sha.UpdateData((unsigned char*)&seed, 4);
			sha.UpdateBigNumbers(&K, NULL);
			sha.Finalize();

			if (memcmp(sha.GetDigest(), msg->digest.data(), 20))
			{
				int i = 0;
				printf("failed\n");
				abort();
#if 0
				packet.Initialize(SMSG_AUTH_RESPONSE, 1);
				packet << unsigned char(AUTH_FAILED);

				SendPacket(packet);

				sLog.outError("WorldSocket::HandleAuthSession: Sent Auth Response (authentification failed).");
				return -1;
#endif
			}
			else {
				printf("sha1 ok\n");
			}
		}
		if (GGListener::Instance().TouchSession(
			socket_handle,
			[rsp, &K](GGSession* s)
			{
				unsigned int speed = s->GetSpeed();
				s->SetKey(K.AsByteArray(), 40);
			}
		)) {
			auto rspmg = std::make_shared<cs::SMAuthResponse>();
			rspmg->code = (decltype(rspmg->code))ResponseCodes_e::AUTH_OK;
			GGListener::Instance().SendMsg(socket_handle, rspmg);
			auto tmp_session = PlayerMgr::Instance().GetSessionBySocket(socket_handle);
			if (!tmp_session) {
				tmp_session = std::make_shared<Session>();
				tmp_session->socket_handle = socket_handle;
				tmp_session->account_id = rsp->GetDataSet()->GetByFieldName("id")->Get<long long>();
				PlayerMgr::Instance().session_hash_[socket_handle] = tmp_session;
				tmp_session->timer_wp = cftf::timer::Timer::Instance().SetTimeoutWpEx(
					1000 * 10,
					[socket_handle](const int event, const cftc::Args* args)
					{
						if (event == cftc::xtimer::TIMER_EXEC_EVENT) {
							PlayerMgr::Instance().session_hash_.erase(socket_handle);
						}
					},
					PlayerMgr::Instance().attacher_.get());
			}
			else {
				cftf::timer::Timer::Instance().ModifyTime(tmp_session->timer_wp, 1000 * 10);
			}
		}
	}

	void PlayerMgr::InternalCharEnum(int socket_handle, const std::shared_ptr<cs::CMCharEnum> msg, const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
	{
		auto rspmsg = std::make_shared<cs::SMCharEnum>();
		if (!rsp->IsOk()) {
			return;
		}
		if (rsp->GetDataSet()->IsEof()) {
			return;
		}
		auto char_info = std::make_shared<cs::MFPlayerInfo>();
		unsigned int counter = rsp->GetDataSet()->GetByFieldName("guid")->Get<unsigned int>();
		char_info->guid = counter ? (unsigned long long)(counter) | (unsigned long long(HighGuid_e::HIGHGUID_PLAYER) << 48) : 0;
		char_info->name = rsp->GetDataSet()->GetByFieldName("name")->Get<decltype(char_info->name)>();
		char_info->race = rsp->GetDataSet()->GetByFieldName("race")->Get<decltype(char_info->race)>();
		char_info->class_ = rsp->GetDataSet()->GetByFieldName("class")->Get<decltype(char_info->class_)>();
		char_info->gender = rsp->GetDataSet()->GetByFieldName("gender")->Get<decltype(char_info->gender)>();

		unsigned int player_bytes = rsp->GetDataSet()->GetByFieldName("playerBytes")->Get<unsigned int>();
		char_info->skin = (unsigned char)player_bytes;
		char_info->face = (unsigned char)(player_bytes >> 8);
		char_info->hair_style = (unsigned char)(player_bytes >> 16);
		char_info->hair_color = (unsigned char)(player_bytes >> 24);

		unsigned int player_bytes2 = rsp->GetDataSet()->GetByFieldName("playerBytes2")->Get<unsigned int>();
		char_info->facial_hair = (unsigned char)(player_bytes2 & 0xFF);

		char_info->level = rsp->GetDataSet()->GetByFieldName("level")->Get<decltype(char_info->level)>();
		char_info->zone = rsp->GetDataSet()->GetByFieldName("zone")->Get<decltype(char_info->zone)>();
		char_info->map = rsp->GetDataSet()->GetByFieldName("map")->Get<decltype(char_info->map)>();
		char_info->x = rsp->GetDataSet()->GetByFieldName("position_x")->Get<decltype(char_info->x)>();
		char_info->y = rsp->GetDataSet()->GetByFieldName("position_y")->Get<decltype(char_info->y)>();
		char_info->z = rsp->GetDataSet()->GetByFieldName("position_z")->Get<decltype(char_info->z)>();
		char_info->guild_id = rsp->GetDataSet()->GetByFieldName("guildid")->Get<decltype(char_info->guild_id)>();

		unsigned int char_flags = 0;
		unsigned int playerFlags = rsp->GetDataSet()->GetByFieldName("playerFlags")->Get<unsigned int>();
		unsigned int atLoginFlags = rsp->GetDataSet()->GetByFieldName("at_login")->Get<unsigned int>();
		if (playerFlags & (unsigned int)PlayerFlags_e::PLAYER_FLAGS_HIDE_HELM)
		{
			char_flags |= (unsigned int)CharacterFlags_e::CHARACTER_FLAG_HIDE_HELM;
		}
		if (playerFlags & (unsigned int)PlayerFlags_e::PLAYER_FLAGS_HIDE_CLOAK)
		{
			char_flags |= (unsigned int)CharacterFlags_e::CHARACTER_FLAG_HIDE_CLOAK;
		}
		if (playerFlags & (unsigned int)PlayerFlags_e::PLAYER_FLAGS_GHOST)
		{
			char_flags |= (unsigned int)CharacterFlags_e::CHARACTER_FLAG_GHOST;
		}
		if (atLoginFlags & (unsigned int)AtLoginFlags_e::AT_LOGIN_RENAME)
		{
			char_flags |= (unsigned int)CharacterFlags_e::CHARACTER_FLAG_RENAME;
		}
		char_info->char_flags = playerFlags;
		char_info->first_login = atLoginFlags & (unsigned int)AtLoginFlags_e::AT_LOGIN_FIRST ? 1 : 0;
		{
			//pet_display_id
			//pet_level
			//pet_family
			std::string equipmentCache = rsp->GetDataSet()->GetByFieldName("equipmentCache")->Get<std::string>();
			std::vector<std::string> strs;
			cftc::util::Split(equipmentCache, strs, ' ');
			for (unsigned int i = 0; i < (unsigned int)EquipmentSlots_::EQUIPMENT_SLOT_END; ++i) {
				unsigned int item_id = cftc::ducktype::XValue(strs.at(i * 2)).Get<unsigned int>();
				auto p = std::make_shared<cs::MFInventory>();
				auto item_meta = mt::Item::GetElement(item_id);
				if (item_meta) {
					p->displayer_info_id = item_meta->displayid;
					p->inventory_type = item_meta->InventoryType;
				}
				char_info->inventory_list.push_back(p);
			}
		}
		rspmsg->char_list.push_back(char_info);
		GGListener::Instance().SendMsg(socket_handle, rspmsg);
	}

	void PlayerMgr::HandlePlayerLogin(Session* session)
	{
		ObjectGuid playerGuid(session->guid);
		auto pCurrChar = std::make_shared<battle::object::Player>();	
		pCurrChar->GetMotionMaster()->Initialize();

		if (!pCurrChar->LoadFromDB(playerGuid, session->datasets))         /// Could not load character from database, cancel login
		{
#if 0
			/* Disconnect the game client */
			KickPlayer();
#endif

			/* Remove references to avoid dangling pointers */
			return;
		}
#if 0
		/* Validation check completely, assign player to WorldSession::_player for later use */
		SetPlayer(pCurrChar);
#endif
		{
			auto msg = std::make_shared<cs::SMLoginVerifyWorld>();
			msg->map_id = pCurrChar->GetMapId();
			msg->pos.x = pCurrChar->GetPositionX();
			msg->pos.y = pCurrChar->GetPositionY();
			msg->pos.z = pCurrChar->GetPositionZ();
			msg->pos.o = pCurrChar->GetOrientation();
			GGListener::Instance().SendMsg(session->GetSocketHandle(), msg);
		}

		{
			auto msg = std::make_shared<cs::SMAccountDataTimes>();
			for (int i = 0; i < 32; ++i) {
				msg->data.push_back(0);
			}
			GGListener::Instance().SendMsg(session->GetSocketHandle(), msg);
		}

		/* 1.12.1 does not have SMSG_MOTD, so we send a server message */
		/* Used for counting number of newlines in MOTD */

		// Send MOTD
		{
			unsigned int linecount = 0;
			/* The MOTD itself */
			std::string str_motd = World::Instance().GetMotd();
			/* Used for tracking our position within the MOTD while iterating through it */
			std::string::size_type pos = 0, nextpos;

			/* Find the next occurance of @ in the string
			 * This is how newlines are represented */
			while ((nextpos = str_motd.find('@', pos)) != std::string::npos)
			{
				/* If these are not equal, it means a '@' was found
				 * These are used to represent newlines in the string
				 * It is set by the code above here */
				if (nextpos != pos)
				{
					/* Send the player a system message containing the substring from pos to nextpos - pos */
#if 0
					ChatHandler(pCurrChar).PSendSysMessage("%s", str_motd.substr(pos, nextpos - pos).c_str());
#endif
					++linecount;
				}
				pos = nextpos + 1;
			}
			/* There are no more newlines in our MOTD, so we send whatever is left */
			if (pos < str_motd.length())
			{
#if 0
				ChatHandler(pCurrChar).PSendSysMessage("%s", str_motd.substr(pos).c_str());
#endif
			}
			cftf::loger::SysLog::Instance().Deubg("WORLD: Sent motd (SMSG_MOTD)");
		}

#if 0
		/* Attempt to load guild for player */
		if (auto resultGuild = session->GetDataSet(PLAYER_LOGIN_QUERY_LOADGUILD))
		{
			/* We're in a guild, so set the player's guild data to represent that */
			pCurrChar->SetInGuild(resultGuild->GetByFieldIdx(0)->Get<unsigned int>());
			pCurrChar->SetRank(resultGuild->GetByFieldIdx(1)->Get<unsigned int>());
		}
		/* Player thinks they have a guild, but it isn't in the database. Clear that information */
		else if (pCurrChar->GetGuildId())
		{
			pCurrChar->SetInGuild(0);
			pCurrChar->SetRank(0);
		}
#endif

#if 0
		/* Player is in a guild
		 * TODO: Can we move this code into the block above? Not sure why it's down here */
		if (pCurrChar->GetGuildId() != 0)
		{
			/* Get guild based on what we set the player's guild to above */
			Guild* guild = sGuildMgr.GetGuildById(pCurrChar->GetGuildId());

			/* More checks to see if they're in a guild? I'm sure this is redundant */
			if (guild)
			{
				/* Build MOTD packet and send it to the player */
				data.Initialize(SMSG_GUILD_EVENT, (1 + 1 + guild->GetMOTD().size() + 1));
				data << uint8(GE_MOTD);
				data << uint8(1);
				data << guild->GetMOTD();
				SendPacket(&data);
				DEBUG_LOG("WORLD: Sent guild-motd (SMSG_GUILD_EVENT)");

				/* Let everyone in the guild know you've just signed in */
				guild->BroadcastEvent(GE_SIGNED_ON, pCurrChar->GetObjectGuid(), pCurrChar->GetName());
			}
			/* If the player is not in a guild */
			else
			{
				sLog.outError("Player %s (GUID: %u) marked as member of nonexistent guild (id: %u), removing guild membership for player.",
					pCurrChar->GetName(),
					pCurrChar->GetGUIDLow(),
					pCurrChar->GetGuildId());

				/* Set guild to 0 (again) */
				pCurrChar->SetInGuild(0);
			}
		}
#endif

		/* Don't let the player get stuck logging in with no corpse */
		if (!pCurrChar->IsAlive())
		{
			pCurrChar->SendCorpseReclaimDelay(true);
		}

		/* Sends information required before the player can be added to the map
		 * TODO: See if we can send information about game objects here (prevent alt+f4 through object) */
		pCurrChar->SendInitialPacketsBeforeAddToMap();

		/* If it's the player's first login, send a cinematic */
		if (!pCurrChar->getCinematic())
		{
			pCurrChar->setCinematic(1);

			/* Set the start location to the player's racial starting point */
			if (auto const* rEntry = dbc::ChrRacesEntry::GetElement(pCurrChar->getRace()))
			{
				pCurrChar->SendCinematicStart(rEntry->CinematicSequence);
			}
		}

		unsigned int miscRequirement = 0;
		
		AreaLockStatus lockStatus = AREA_LOCKSTATUS_OK;
		if (auto const* at = mt::AreaTrigger::GetElement(pCurrChar->GetMapId()))
		{
			lockStatus = pCurrChar->GetAreaTriggerLockStatus(at, miscRequirement);
		}
		else
		{
			// Some basic checks in case of a map without areatrigger
			auto const* mapEntry = dbc::MapEntry::GetElement(pCurrChar->GetMapId());
			if (!mapEntry)
			{
				lockStatus = AREA_LOCKSTATUS_UNKNOWN_ERROR;
			}
		}

		/* This code is run if we can not add the player to the map for some reason */
		if (lockStatus != AREA_LOCKSTATUS_OK || !pCurrChar->GetMap()->Add(pCurrChar.get()))
		{
			/* Attempt to find an areatrigger to teleport the player for us */
			auto const* at = mt::AreaTrigger::GetGoBackTrigger(pCurrChar->GetMapId());
			if (at)
			{
				lockStatus = pCurrChar->GetAreaTriggerLockStatus(at, miscRequirement);
			}

			/* We couldn't find an areatrigger to teleport, so just move the player back to their home bind */
			if (!at || lockStatus != AREA_LOCKSTATUS_OK || !pCurrChar->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, pCurrChar->GetOrientation()))
			{
				pCurrChar->TeleportToHomebind();
			}
		}
#if 0
		sObjectAccessor.AddObject(pCurrChar);
#endif
		cftf::loger::SysLog::Instance().Info(std::format("Player {} added to map {}", pCurrChar->GetName(), pCurrChar->GetMapId()));
#if 0
		/* send the player's social lists */
		pCurrChar->GetSocial()->SendFriendList();
		pCurrChar->GetSocial()->SendIgnoreList();

		/* Send packets that must be sent only after player is added to the map */
		pCurrChar->SendInitialPacketsAfterAddToMap();

		/* Mark player as online in the database */
		static SqlStatementID updChars;
		static SqlStatementID updAccount;

		SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE `characters` SET `online` = 1 WHERE `guid` = ?");
		stmt.PExecute(pCurrChar->GetGUIDLow());

#ifdef ENABLE_PLAYERBOTS
		if (pCurrChar->GetSession()->GetRemoteAddress() != "bot")
		{
#endif
			stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE `account` SET `active_realm_id` = ? WHERE `id` = ?");
			stmt.PExecute(realmID, GetAccountId());
#ifdef ENABLE_PLAYERBOTS
		}
#endif

		/* Sync player's in-game time with server time */
		pCurrChar->SetInGameTime(GameTime::GetGameTimeMS());

		/* Send logon notification to player's group
		 * This is sent after player is added to the world so that player receives it too */
		if (Group* group = pCurrChar->GetGroup())
		{
			group->SendUpdate();
		}

		/* Inform player's friends that player has come online */
		sSocialMgr.SendFriendStatus(pCurrChar, FRIEND_ONLINE, pCurrChar->GetObjectGuid(), true);

		/* Load the player's corpse if it exists, or resurrect the player if not */
		pCurrChar->LoadCorpse();

		/* If the player is dead, we need to set them as a ghost and increase movespeed */
		if (pCurrChar->m_deathState != ALIVE)
		{
			/* If player is a night elf, wisp racial should be applied */
			if (pCurrChar->getRace() == RACE_NIGHTELF)
			{
				pCurrChar->CastSpell(pCurrChar, 20584, true);   // auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form)
			}

			/* Apply ghost spell to player */
			pCurrChar->CastSpell(pCurrChar, 8326, true);        // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?)

			/* Allow player to walk on water */
			pCurrChar->SetWaterWalk(true);
		}

		/* If player is on a taxi, continue their flight */
		pCurrChar->ContinueTaxiFlight();

		/* Load pet if player has one
		 * If the player is dead or on a taxi, the pet will be remembered as a temporary summon */
		pCurrChar->LoadPet();

		/* If we're running an FFA PvP realm and the player isn't a GM, mark them as PvP flagged */
		if (sWorld.IsFFAPvPRealm() && !pCurrChar->isGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
		{
			pCurrChar->SetFFAPvP(true);
		}

		if (pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP))
		{
			pCurrChar->SetContestedPvP();
		}

		/* Apply onLogon requests (such as talent resets) */
		if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_SPELLS))
		{
			pCurrChar->resetSpells();
			SendNotification(LANG_RESET_SPELLS);
		}

		if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
		{
			pCurrChar->resetTalents(true);
			SendNotification(LANG_RESET_TALENTS);               // we can use SMSG_TALENTS_INVOLUNTARILY_RESET here
		}

		// Used by Eluna
#ifdef ENABLE_ELUNA
		if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST))
		{
			sEluna->OnFirstLogin(pCurrChar);
		}
#endif /* ENABLE_ELUNA */


		/* We've done what we need to, remove the flag */
		if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST))
		{
			pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST);
		}

		/* If the server is shutting down, show shutdown time remaining */
		if (sWorld.IsShutdowning())
		{
			sWorld.ShutdownMsg(true, pCurrChar);
		}

		/* If player should have all taxi paths, give them to the player */
		if (sWorld.getConfig(CONFIG_BOOL_ALL_TAXI_PATHS))
		{
			pCurrChar->SetTaxiCheater(true);
		}

		/* Send GM notifications */
		if (pCurrChar->isGameMaster())
		{
			SendNotification(LANG_GM_ON);
		}

		if (!pCurrChar->isGMVisible())
		{
			SendNotification(LANG_INVISIBLE_INVISIBLE);
			SpellEntry const* invisibleAuraInfo = sSpellStore.LookupEntry(sWorld.getConfig(CONFIG_UINT32_GM_INVISIBLE_AURA));
			if (invisibleAuraInfo && IsSpellAppliesAura(invisibleAuraInfo))
			{
				pCurrChar->CastSpell(pCurrChar, invisibleAuraInfo, true);
			}
		}

		std::string IP_str = GetRemoteAddress();
		sLog.outChar("Account: %d (IP: %s) Login Character:[%s] (guid: %u)",
			GetAccountId(), IP_str.c_str(), pCurrChar->GetName(), pCurrChar->GetGUIDLow());

		/* Make player stand up if they're not already stood up and not stunned */
		if (!pCurrChar->IsStandState() && !pCurrChar->hasUnitState(UNIT_STAT_STUNNED))
		{
			pCurrChar->SetStandState(UNIT_STAND_STATE_STAND);
		}

		m_playerLoading = false;

		// Used by Eluna
#ifdef ENABLE_ELUNA
		sEluna->OnLogin(pCurrChar);
#endif /* ENABLE_ELUNA */

		/* Used for movement */
		m_clientTimeDelay = 0;

		/* Used for looting */
		pCurrChar->lastTimeLooted = time(NULL);
#endif
		socket_hash_[session->GetSocketHandle()] = pCurrChar;
	}

}